package main

import (
	"fmt"
	pb "g-cache/biz/model/gcache"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"strings"
	"sync"

	"github.com/golang/protobuf/proto"

	"g-cache/pkg/cache"
	"g-cache/pkg/consistenthash"
)

const (
	defaultBasePath = "/_g-cache/" // 服务预期的路径前缀（不变）
	defaultReplicas = 50
)

// HTTPPool implements PeerPicker for a pool of HTTP peers.
type HTTPPool struct {
	self        string                 // 当前节点的基础URL（如 "http://localhost:8001"）
	basePath    string                 // 服务处理的路径前缀（默认 /_g-cache/）
	mu          sync.Mutex             // 保护 peers 和 httpGetters 的互斥锁
	peers       *consistenthash.Map    // 一致性哈希映射，用于选择节点
	httpGetters map[string]*httpGetter // 节点对应的 HTTP 客户端，key 是节点URL
}

// NewHTTPPool initializes an HTTP pool of peers.
func NewHTTPPool(self string) *HTTPPool {
	return &HTTPPool{
		self:     self,
		basePath: defaultBasePath,
	}
}

// Log info with server name（不变，用于带节点标识的日志）
func (p *HTTPPool) Log(format string, v ...interface{}) {
	log.Printf("[Server %s] %s", p.self, fmt.Sprintf(format, v...))
}

// ServeHTTP handle all http requests（核心修改：容错未预期路径）
func (p *HTTPPool) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// 1. 关键修改：对未匹配 basePath 的路径，返回 404 而非 panic
	if !strings.HasPrefix(r.URL.Path, p.basePath) {
		// 记录警告日志（便于排查非法请求）
		p.Log("warning: unexpected path %s (expected prefix %s)", r.URL.Path, p.basePath)
		// 返回 404 错误（符合 HTTP 规范，避免服务崩溃）
		http.NotFound(w, r)
		return
	}

	// 2. 处理合法路径（原有逻辑不变）
	p.Log("%s %s", r.Method, r.URL.Path)
	// 拆分路径：格式为 /<basePath>/<groupName>/<key>，需拆出 groupName 和 key
	// 先去掉 basePath 前缀，再按 "/" 分割（最多分 2 段，避免 key 含 "/" 时被拆分）
	parts := strings.SplitN(r.URL.Path[len(p.basePath):], "/", 2)
	if len(parts) != 2 {
		// 路径格式错误（如仅 /_g-cache/group 无 key），返回 400 非法请求
		http.Error(w, "bad request: path format must be /"+p.basePath+"<group>/<key>", http.StatusBadRequest)
		return
	}

	groupName := parts[0]
	key := parts[1]

	// 3. 查找对应的缓存组（原有逻辑不变）
	group := cache.GetGroup(groupName)
	if group == nil {
		http.Error(w, "no such group: "+groupName, http.StatusNotFound)
		return
	}

	// 4. 从缓存组获取数据（原有逻辑不变）
	view, err := group.Get(key)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	// 5. 序列化数据为 Protobuf 格式并返回（原有逻辑不变）
	body, err := proto.Marshal(&pb.Response{Value: view.ByteSlice()})
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/octet-stream")
	w.Write(body)
}

// Set updates the pool's list of peers（原有逻辑不变）
func (p *HTTPPool) Set(peers ...string) {
	p.mu.Lock()
	defer p.mu.Unlock()
	p.peers = consistenthash.New(defaultReplicas, nil)
	p.peers.Add(peers...)
	p.httpGetters = make(map[string]*httpGetter, len(peers))
	for _, peer := range peers {
		// 每个节点的请求路径需拼接 basePath（确保请求到正确的处理逻辑）
		p.httpGetters[peer] = &httpGetter{baseURL: peer + p.basePath}
	}
}

// PickPeer picks a peer according to key（原有逻辑不变）
func (p *HTTPPool) PickPeer(key string) (cache.PeerGetter, bool) {
	p.mu.Lock()
	defer p.mu.Unlock()
	// 一致性哈希选择节点，排除当前节点自身
	if peer := p.peers.Get(key); peer != "" && peer != p.self {
		p.Log("Pick peer %s", peer)
		return p.httpGetters[peer], true
	}
	return nil, false
}

// 确保 HTTPPool 实现 cache.PeerPicker 接口（编译期检查，原有逻辑不变）
var _ cache.PeerPicker = (*HTTPPool)(nil)

// httpGetter 实现 cache.PeerGetter 接口，用于向其他节点发送 HTTP 请求（原有逻辑不变）
type httpGetter struct {
	baseURL string // 目标节点的基础 URL（含 basePath，如 "http://localhost:8002/_g-cache/"）
}

func (h *httpGetter) Get(in *pb.Request, out *pb.Response) error {
	// 拼接请求 URL：baseURL + 编码后的 groupName + / + 编码后的 key
	// 用 url.QueryEscape 处理特殊字符（如 key 含空格、& 等）
	u := fmt.Sprintf(
		"%v%v/%v",
		h.baseURL,
		url.QueryEscape(in.GetGroup()),
		url.QueryEscape(in.GetKey()),
	)

	// 发送 HTTP GET 请求
	res, err := http.Get(u)
	if err != nil {
		return fmt.Errorf("failed to get from %s: %v", u, err)
	}
	defer res.Body.Close()

	// 检查响应状态码（非 200 视为错误）
	if res.StatusCode != http.StatusOK {
		return fmt.Errorf("server %s returned status: %v", u, res.Status)
	}

	// 读取响应体
	bytes, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return fmt.Errorf("failed to read response from %s: %v", u, err)
	}

	// 反序列化 Protobuf 数据
	if err = proto.Unmarshal(bytes, out); err != nil {
		return fmt.Errorf("failed to decode response from %s: %v", u, err)
	}

	return nil
}

// 确保 httpGetter 实现 cache.PeerGetter 接口（编译期检查，原有逻辑不变）
var _ cache.PeerGetter = (*httpGetter)(nil)
